﻿using DryIoc;
using Prism.Ioc;
using StarryEdge.Core;
using StarryEdge.Core.Device;
using StarryEdge.Core.Enums;
using StarryEdge.Core.Manager;
using StarryEdge.Core.Models;
using StarryEdge.Core.Models.Interact;
using StarryEdge.Core.Parameter;
using StarryEdge.Parameter;
using StarryEdge.WPF.Tool;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace StarryEdge.Domains
{
    public class TaskManager : ITaskManager
    {
        private CancellationTokenSource _taskToken;
        private ConcurrentDictionary<string, TaskEntity> _taskOldParameter = new ConcurrentDictionary<string, TaskEntity>();
        private ConcurrentDictionary<string, TaskEntity> _taskNewParameter = new ConcurrentDictionary<string, TaskEntity>();
        private ConcurrentDictionary<string, TaskEntity> _taskExecute = new ConcurrentDictionary<string, TaskEntity>();
        private int _taskStatus = 0;
        private TemporaryParameter _temporary;
        private SystemConfigParameter _systemConfig;
        private IStarryEdgeLog _starryEdgeLog;
        private IDialogHost _dialogHost;
        private IObjectManager _objectManager;
        private IContainerProvider _containerProvider;
        public int TaskStatus => _taskStatus;

        public TaskManager(ITemporaryParameter temporary, ISystemConfigParameter systemConfig, IStarryEdgeLog starryEdgeLog, 
            IDialogHost dialogHost, IObjectManager objectManager, IContainerProvider containerProvider)
        {
            _temporary = (TemporaryParameter)temporary;
            _systemConfig = (SystemConfigParameter)systemConfig;
            _starryEdgeLog = starryEdgeLog;
            _dialogHost = dialogHost;
            _objectManager = objectManager;
            _taskToken = new CancellationTokenSource();
            _containerProvider = containerProvider;
        }

        private void InitContext<T>(ScanSignalModel<T> signal, List<InteractAddressModel<T>> interactAddressModel, bool isUpdate = false)
        {
            var iAddressList = interactAddressModel.Where(x => x.ServiceName == signal.Name);
            foreach (var item in iAddressList)
            {
                var task = _objectManager.ResolveTask(item.TaskType);
                if (task.IsUpdate || !isUpdate)
                {
                    var handlerType = typeof(HandleContext<T>);
                    object obj = Activator.CreateInstance(handlerType);
                    var objProper = handlerType.GetProperties();
                    objProper.Where(x => x.Name == "TaskName").FirstOrDefault()?.SetValue(obj, item.TaskType);
                    objProper.Where(x => x.Name == "DeviceType").FirstOrDefault()?.SetValue(obj, item.DeviceType);
                    objProper.Where(x => x.Name == "AddressingMethod").FirstOrDefault()?.SetValue(obj, signal.AddressingMethod);
                    if (typeof(T).Name == "Int32")
                    {
                        objProper.Where(x => x.Name == "Address_Signal").FirstOrDefault()?.SetValue(obj, Convert.ToInt32($"{signal.Address_Start}") + (item.Index - 1));
                    }
                    else
                    {
                        objProper.Where(x => x.Name == "Address_Signal").FirstOrDefault()?.SetValue(obj, $"{signal.Address_Start}.{item.Label}");
                    }
                    objProper.Where(x => x.Name == "Address_Struct").FirstOrDefault()?.SetValue(obj, item.Address_Struct);
                    objProper.Where(x => x.Name == "Struct_Length").FirstOrDefault()?.SetValue(obj, item.Struct_Length);
                    objProper.Where(x => x.Name == "DeviceIndex").FirstOrDefault()?.SetValue(obj, item.DeviceIndex);
                    objProper.Where(x => x.Name == "ServiceName").FirstOrDefault()?.SetValue(obj, item.ServiceName);
                    objProper.Where(x => x.Name == "Extra").FirstOrDefault()?.SetValue(obj, item.Extra);
                    objProper.Where(x => x.Name == "IsScheduledTask").FirstOrDefault()?.SetValue(obj, item.IsScheduledTask);
                    string key = $"{signal.Name}{item.TaskType}{item.DeviceType.GetHashCode()}{item.Address_Struct}{item.DeviceIndex}";
                    TaskEntity taskEntity = new TaskEntity(_starryEdgeLog, _dialogHost, _containerProvider)
                    {
                        Key = key,
                        ServiceName = signal.Name,
                    };
                    taskEntity.SetObject(obj, _objectManager.ResolveTask(item.TaskType));
                    if (isUpdate)
                    {
                        _taskNewParameter.TryAdd(key, taskEntity);
                    }
                    else
                    {
                        _taskOldParameter.TryAdd(key, taskEntity);
                    }
                    task.IsUpdate = false;
                }
            }
        }

        public bool LoopTask()
        {
            if (_taskToken.Token.IsCancellationRequested) { _taskToken = new CancellationTokenSource(); }
            try
            {
                _taskExecute.Clear();
                _temporary.StatusDevices.Clear();
                foreach (var signal in _systemConfig.StringSignalList)
                {
                    _temporary.StatusDevices.Add(signal.Name, 0);
                    InitContext(signal, _systemConfig.StringIAddressList.ToList());
                }
                foreach (var signal in _systemConfig.AddressSignalList)
                {
                    _temporary.StatusDevices.Add(signal.Name, 0);
                    InitContext(signal, _systemConfig.AddressIAddressList.ToList());
                }
                _taskStatus = 1;
                var device = _containerProvider.Resolve<IDeviceFactory>();
                foreach (var signal in _systemConfig.StringSignalList)
                {
                    if (signal.DeviceType == DeviceTypes.NONE)
                    {
                        continue;
                    }
                    LoopSignal(signal, _systemConfig.StringIAddressList, device);
                }
                foreach (var signal in _systemConfig.AddressSignalList)
                {
                    if (signal.DeviceType == DeviceTypes.NONE)
                    {
                        continue;
                    }
                    LoopSignal(signal, _systemConfig.AddressIAddressList, device);
                }
                _temporary.Status_Software = true;
                return true;
            }
            catch (Exception ex)
            {
                _starryEdgeLog.LogRun($"任务启动异常：{ex}", LogLevel.Error);
                _taskStatus = 0;
            }
            _temporary.Status_Software = _taskStatus == 1;
            return false;
        }

        private readonly object _lock = new object();

        private void LoopSignal<T>(ScanSignalModel<T> scanSignal, ObservableCollection<InteractAddressModel<T>> interactAddressModels, IDeviceFactory device)
        {
            _starryEdgeLog.LogRun($"扫描线程 [{scanSignal.Name}] 启动");
            ThreadPool.QueueUserWorkItem(new WaitCallback(signal =>
            {
                int count_Heartbeat = 0;
                ScanSignalModel<T> ssModel = (ScanSignalModel<T>)signal;
                IPLC<T> plc = device.GetDevice<IPLC<T>>(x => x.ServiceName == ssModel.Name && x.TaskName == "扫描线程");
                if (plc == null)
                {
                    _dialogHost.DialogShow($"[{ssModel.Name}]任务找不到[{ssModel.DeviceType}]设备!!!", LogLevel.Error, hostDialog: PopUpTypes.Global);
                    _taskStatus = 0;
                    _taskToken.Cancel();
                }
                else
                {
                    try
                    {
                        bool[] bools = new bool[ssModel.Length_Resection];
                        PLCSignalModel signalData = new PLCSignalModel(ssModel.Length_Signal, ssModel.Length_Resection);
                        StringBuilder key = new StringBuilder();
                        int count = 0;
                        IEnumerable<InteractAddressModel<T>> cmdlist = interactAddressModels.Where(x => x.ServiceName == ssModel.Name && !x.IsScheduledTask);
                        IEnumerable<InteractAddressModel<T>> scheduledTask = interactAddressModels.Where(x => x.ServiceName == ssModel.Name && x.IsScheduledTask);
                        IEnumerable<PLCResectionModel> resections = _systemConfig.PLCResectionList.Where(x => x.ServiceName == ssModel.Name);
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();
                        while (!_taskToken.Token.IsCancellationRequested)
                        {
                            try
                            {
                                var mytasks = _taskNewParameter.Where(x => x.Value.ServiceName == ssModel.Name).ToArray();
                                if (mytasks.Length > 0)
                                {
                                    foreach (var pair in mytasks)
                                    {
                                        var old = _taskOldParameter.ContainsKey(pair.Key) ? _taskOldParameter[pair.Key] : default;
                                        if (old != null && !old.IsRun)
                                        {
                                            _taskOldParameter[old.Key] = pair.Value;
                                            _taskNewParameter.TryRemove(pair.Key, out _);
                                        }
                                    }
                                }
                                if (plc.ReadClass(ssModel.Address_Start, signalData) != null)
                                {
                                    foreach (var item in cmdlist)
                                    {
                                        int index = item.Index - 1;
                                        if (signalData.Cmd[index] == 1 || (signalData.Cmd[index] != _temporary.StatusDevices[ssModel.Name] && item.TaskType.Contains("设备状态")))
                                        {
                                            key.Clear();
                                            if (item.TaskType.Contains("设备状态"))
                                            {
                                                if (_temporary.StatusDevices[ssModel.Name] == signalData.Cmd[index])
                                                {
                                                    continue;
                                                }
                                                _temporary.StatusDevices[ssModel.Name] = signalData.Cmd[index];
                                                if (ssModel.Index == 0 && ssModel.Name.Contains("主"))
                                                {
                                                    _temporary.Status_Device = _temporary.StatusDevices[ssModel.Name];
                                                }
                                            }
                                            try
                                            {
                                                key.Append($"{item.ServiceName}{item.TaskType}{item.DeviceType.GetHashCode()}{item.Address_Struct}{item.DeviceIndex}");
                                                if (_taskExecute.ContainsKey(key.ToString()) || !_taskExecute.TryAdd(key.ToString(), default)) { continue; }
                                                ThreadPool.QueueUserWorkItem(new WaitCallback(sender => {
                                                    TaskEntity taskEntity = sender as TaskEntity;
                                                    taskEntity.IsRun = true;
                                                    try
                                                    {
                                                        taskEntity.Handle(plc);
                                                    }
                                                    catch (Exception ex)
                                                    {
                                                        _starryEdgeLog.LogRun($"接口执行异常：{ex}", Core.Enums.LogLevel.Error);
                                                        Thread.Sleep(1000);
                                                    }
                                                    _taskExecute.TryRemove(taskEntity.Key, out _);
                                                    taskEntity.IsRun = false;
                                                }),
                                                _taskOldParameter[key.ToString()]);
                                            }
                                            catch (Exception ex)
                                            {
                                                _starryEdgeLog.LogRun($"[{ssModel.Name}] [{item.TaskType}]信号触发异常：{ex}", LogLevel.Error);
                                            }
                                        }
                                    }
                                    foreach (var resection in resections)
                                    {
                                        int index = resection.Index - 1;
                                        if (signalData.Resection[index] && !bools[index])
                                        {
                                            resection.IsExcision = true;
                                            _starryEdgeLog.LogRun($"[{ssModel.Name}] 注意：[{resection.Name}] 切除功能已启用！！！", LogLevel.Warning);
                                        }
                                        else if (!signalData.Resection[index] && bools[index])
                                        {
                                            resection.IsExcision = false;
                                            _starryEdgeLog.LogRun($"[{ssModel.Name}] 注意：[{resection.Name}] 切除功能已关闭！！！", LogLevel.Success);
                                        }
                                        bools[index] = signalData.Resection[index];
                                    }
                                }
                                else
                                {
                                    count += 1;
                                    if (count == 3)
                                    {
                                        _dialogHost.DialogShow($"[{ssModel.Name}] 连续扫描信号异常，退出任务！！！", LogLevel.Error, hostDialog: PopUpTypes.Global);
                                        _taskToken.Cancel();
                                        break;
                                    }
                                    Thread.Sleep(200);
                                    continue;
                                }
                                count_Heartbeat++;
                                if (count_Heartbeat >= 20)
                                {
                                    plc.WriteSingle(ssModel.Heartbeat, (short)1, 0);
                                    count_Heartbeat = 0;
                                }
                                count = 0;
                                if (stopwatch.ElapsedMilliseconds > 950)
                                {
                                    foreach (var item in scheduledTask)
                                    {
                                        try
                                        {
                                            key.Clear();
                                            key.Append($"{item.ServiceName}{item.TaskType}{item.DeviceType.GetHashCode()}{item.Address_Struct}{item.DeviceIndex}");
                                            if (_taskExecute.ContainsKey(key.ToString()) || !_taskExecute.TryAdd(key.ToString(), default)) { continue; }
                                            ThreadPool.QueueUserWorkItem(new WaitCallback(sender => {
                                                TaskEntity taskEntity = sender as TaskEntity;
                                                taskEntity.IsRun = true;
                                                try
                                                {
                                                    taskEntity.Handle(plc);
                                                }
                                                catch (Exception ex)
                                                {
                                                    _starryEdgeLog.LogRun($"接口执行异常：{ex}", Core.Enums.LogLevel.Error);
                                                    Thread.Sleep(1000);
                                                }
                                                _taskExecute.TryRemove(taskEntity.Key, out _);
                                                taskEntity.IsRun = false;
                                            }), _taskOldParameter[key.ToString()]);
                                        }
                                        catch (Exception ex)
                                        {
                                            _starryEdgeLog.LogRun($"[{ssModel.Name}] [{item.TaskType}] 定时任务触发异常：{ex}", LogLevel.Error);
                                        }
                                    }
                                    stopwatch.Restart();
                                }
                            }
                            catch (Exception ex)
                            {
                                if (count == 2)
                                {
                                    _dialogHost.DialogShow($"[{ssModel.Name}] 连续扫描信号异常，退出任务！！！", LogLevel.Error, hostDialog: PopUpTypes.Global);
                                    _taskToken.Cancel();
                                    break;
                                }
                                _starryEdgeLog.LogRun($"信号扫描线程异常：{ex}", LogLevel.Error);
                                count++;
                                Thread.Sleep(500);
                            }
                            Thread.Sleep(20);
                        }
                        count = 0;
                        while (_taskExecute.Count != 0)
                        {
                            Thread.Sleep(500);
                            if (count > 60)
                            {
                                _taskExecute.Clear();
                            }
                            _starryEdgeLog.LogRun($"第 [{++count}] 次 等待所有任务退出.....");
                        }
                    }
                    catch (Exception ex)
                    {
                        _dialogHost.DialogShow($"扫描线程执行异常：{ex}", LogLevel.Error, hostDialog: PopUpTypes.Global);
                    }
                }
                var olds = _taskOldParameter.Where(x => x.Value.ServiceName == ssModel.Name);
                foreach (var item in olds)
                {
                    _taskOldParameter.TryRemove(item.Key, out _);
                }
                if (_taskOldParameter.Count == 0)
                {
                    _temporary.Status_Device = 0;
                    _temporary.Status_Software = false;
                    _containerProvider.Resolve<IDeviceFactory>().Disconnect();
                    _starryEdgeLog.LogRun("扫描系统 停止");
                }
            }), scanSignal);
        }

        public void StopTask() => _taskToken.Cancel();

        public bool Update()
        {
            if (_taskStatus == 1)
            {
                lock (_lock)
                {
                    foreach (var signal in _systemConfig.StringSignalList)
                    {
                        if (!_temporary.StatusDevices.ContainsKey(signal.Name))
                        {
                            _temporary.StatusDevices.Add(signal.Name, 0);
                        }
                        InitContext(signal, _systemConfig.StringIAddressList.ToList(), true);
                    }
                    foreach (var signal in _systemConfig.AddressSignalList)
                    {
                        if (!_temporary.StatusDevices.ContainsKey(signal.Name))
                        {
                            _temporary.StatusDevices.Add(signal.Name, 0);
                        }
                        InitContext(signal, _systemConfig.AddressIAddressList.ToList(), true);
                    }
                }
                while (_taskNewParameter.Count > 0)
                {
                    Thread.Sleep(200);
                }
            }
            return true;
        }
    }
}
